#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include "log.hpp"
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <unordered_map>


using func_t = std::function<std::string(const std::string&, const std::string&, uint16_t)>;
//typedef std::function<std::string(const std::string&)> func_t;

Log log;

enum 
{
    SOCKET_ERR = 1,
    BIND_ERR
};

std::string defaultip = "0.0.0.0";
uint16_t defaultport = 8080;
const size_t size = 1024;

class UdpServer
{
public:
    UdpServer(const uint16_t port = defaultport, const std::string& ip = defaultip)
        :sockfd_(0)
        ,port_(port)
        ,ip_(ip)
        ,isrunning_(false)
    {}
    void Init()
    {
        //这里创建的套接字是全双工的，允许被同时读写的

        //创建udp socket套接字,可以想象成打开了一个网卡文件
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); //PF_INET
        //创建套接字失败了
        if(socket < 0)
        {
            log(Fatal, "socket create error, socket: %d", sockfd_);
            exit(SOCKET_ERR);
        }
        log(Info, "socket create success ,socket fd: %d" , sockfd_);

    

        // 2.bind socket,要告诉服务器我们的端口号了。将这个套接字地址结构体与这个套接字进行绑定
        struct sockaddr_in local;
        //清空
        bzero(&local, sizeof(local));

        //除了上面打开一个网卡文件用这个AF_INET之外，这里填充结构体的时候也要用到这个字段
        local.sin_family = AF_INET; //表明结构体是使用ipv4的网络通信 ，这个s可以理解为socket,in可以理解为inet(ifconfig)
        local.sin_port = htons(port_); //需要往这个结构体里面填充端口号，需要保证端口号是网络字节序列，因为该端口号是要给对方发送的
        //他就是32位的ip地址,作为一个普通的用户，我们一般认为的ip地址就是字符串，所以我们需要将他转化为整数后，在变成网络序列
        local.sin_addr.s_addr = inet_addr(ip_.c_str()); //string --> uint32_t, 2.uint_32必须是网络序列的


        //local.sin_addr.s_addr = INADDR_ANY; 
        int n = bind(sockfd_, (const struct sockaddr*)&local, sizeof(local));
        if(n < 0)
        {
            log(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        log(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));

    }   
    void CheckUser(struct sockaddr_in& client, const string& clientip, const uint16_t clientport)
    {
        auto iter = online_user_.find(clientip);
        if(iter == online_user_.end())
        {
            online_user_.insert({clientip, client});
            cout << "[" << clientip << ":" << clientport << "] add to online user. " << endl;
        }
    }
    void Broadcast(const string& info, const string clientip, const uint16_t clientport)
    {
        for(const auto& user : online_user_)
        {
            string message = "[";
            message += clientip;
            message += ":";
            message += to_string(clientport);
            message += "]";
            message += info;
            socklen_t len = sizeof(user.second);
            sendto(sockfd_, message.c_str(), message.size(), 0, (const struct sockaddr*)&(user.second), len);
        }
    }


    void Run()
    {
        isrunning_ = true;
        char inbuffer[size];
        while(isrunning_)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);

            ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);
            if(n < 0)
            {
                log(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));
                continue;
            }
            //log(Info, "recvfrom success, errno: %d, err string: %s", errno, strerror(errno));
            
            //将这个消息保存在info中，准备将info转发给所有人
            inbuffer[n] = 0;
            std::string info = inbuffer;
            uint16_t clientport = ntohs(client.sin_port);
            std::string clientip =  inet_ntoa(client.sin_addr); 
            //检查是不是新客户
            CheckUser(client, clientip, clientport);

            Broadcast(info, clientip, clientport);


        }
    }
    ~UdpServer()
    {
        if(sockfd_ > 0)
        {
            close(sockfd_);
        }
    }
private:
    int sockfd_; //网络文件描述符
    std::string ip_; // 任意地址bind
    uint16_t port_; //表明服务器进程的端口号
    bool isrunning_;
    //key:ip, vaule: ip+port
    unordered_map<string, struct sockaddr_in> online_user_;
};